package cn.nn200433.swagger;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.nn200433.common.BaiDuFy;
import cn.nn200433.swagger.entity.Endpoint;
import cn.nn200433.swagger.entity.Parameter;
import cn.nn200433.swagger.entity.Resource;
import cn.nn200433.swagger.entity.SwaggerData;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
import io.swagger.models.HttpMethod;
import io.swagger.parser.OpenAPIParser;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import io.swagger.v3.oas.models.tags.Tag;
import io.swagger.v3.parser.core.models.ParseOptions;

import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;

public class SwaggerToWord {

    /**
     * swagger 转 word
     *
     * @param url          url
     * @param documentName 文档名称
     * @param tplStream    模板文件流
     * @param exportPath   导出路径
     * @param baiDuFy      百度翻译
     * @throws Exception 异常
     * @author song_jx
     */
    public void transform(String url, String documentName, InputStream tplStream, String exportPath, BaiDuFy baiDuFy) throws Exception {
        OpenAPI openAPI = new OpenAPIParser().readLocation(url, null, new ParseOptions()).getOpenAPI();
        SwaggerData viewData = handleSwagger(openAPI, documentName, baiDuFy);
        LoopRowTableRenderPolicy hackLoopTableRenderPolicy = new LoopRowTableRenderPolicy();
        Configure config = Configure.builder()
                .bind("parameters", hackLoopTableRenderPolicy)
                .bind("responses", hackLoopTableRenderPolicy)
                .useSpringEL()
                .build();
        XWPFTemplate template = XWPFTemplate.compile(tplStream, config)
                .render(viewData);
        template.writeToFile(exportPath);
    }

    /**
     * 处理 swagger
     *
     * @param openAPI      开放api
     * @param documentName 文档名称
     * @param baiDuFy      百度翻译
     * @return {@link SwaggerData }
     * @author song_jx
     */
    private SwaggerData handleSwagger(OpenAPI openAPI, String documentName, BaiDuFy baiDuFy) {
        // 数据模型
        Map<String, Schema> schemaMap = openAPI.getComponents().getSchemas();

        DateTime date = DateUtil.date();
        SwaggerData view = new SwaggerData();
        view.setInfo(openAPI.getInfo());
        view.setBasePath(openAPI.getServers().get(0).getUrl());
        view.setSchemes(schemaMap);
        view.setResources(Collections.emptyList());
        view.setYear(date.toString("yyyy"));
        view.setMonth(date.toString("MM"));
        if (StrUtil.isBlank(documentName)) {
            documentName = "XXX接口文档";
        }
        view.setDocumentName(documentName);

        // region 处理接口分组
        Paths paths = openAPI.getPaths();
        if (null == paths) {
            return view;
        }
        List<Endpoint> endpoints = buildApis(paths, schemaMap, baiDuFy);
        List<Resource> resources = buildTags(openAPI.getTags(), endpoints);
        view.setResources(resources);
        // endregion

        return view;
    }

    /**
     * 构建接口
     *
     * @param paths     待处理接口
     * @param schemaMap DTO映射
     * @param baiDuFy   百度翻译
     * @return {@link List }<{@link Endpoint }>
     * @author song_jx
     */
    private List<Endpoint> buildApis(Paths paths, Map<String, Schema> schemaMap, final BaiDuFy baiDuFy) {
        List<Endpoint> endpoints = new ArrayList<Endpoint>(paths.size());
        paths.forEach((url, pathItem) -> {
            Map<HttpMethod, Operation> operationMap = getOperationMap(pathItem);
            if (null == operationMap) {
                return;
            }
            operationMap.forEach((method, operation) -> {
                Endpoint endpoint = new Endpoint();
                endpoint.setUrl(url);
                endpoint.setHttpMethod(method.toString());
                endpoint.setGet(HttpMethod.GET == method);
                endpoint.setDelete(HttpMethod.DELETE == method);
                endpoint.setPut(HttpMethod.PUT == method);
                endpoint.setPost(HttpMethod.POST == method);
                endpoint.setSummary(operation.getSummary());
                endpoint.setDescription(operation.getDescription());
                endpoint.setTags(operation.getTags());

                // region 处理请求参数
                List<Parameter> parameterList = new ArrayList<Parameter>();
                if (null != operation.getParameters()) {
                    operation.getParameters().forEach(para -> {
                        String in = para.getIn();
                        if ("header".equals(in)) {
                            return;
                        }
                        Parameter parameter = new Parameter();
                        parameter.setDescription(para.getDescription());
                        parameter.setType(para.getSchema().getType());
                        parameter.setIn(in);
                        parameter.setName(para.getName());
                        parameter.setRequired(para.getRequired());
                        parameterList.add(parameter);

                    });
                }

                List<String> produces = new ArrayList<String>(2);
                RequestBody requestBody = operation.getRequestBody();
                if (null != requestBody) {
                    requestBody.getContent().forEach((produce, dataType) -> {
                        produces.add(produce);
                        String $ref = dataType.getSchema().get$ref();
                        if (StrUtil.isBlank($ref)) {
                            return;
                        }
                        parameterList.addAll(getSchemaProperties(schemaMap, $ref, baiDuFy));
                    });
                } else {
                    produces.add("application/x-www-form-urlencoded");
                }

                endpoint.setParameters(parameterList);
                endpoint.setParameterSize(parameterList.size());
                endpoint.setProduces(produces);

                // endregion

                // region 响应数据

                ApiResponses responses = operation.getResponses();
                if (null != responses) {
                    ApiResponse apiResponse = responses.get("200");
                    if (null != apiResponse) {
                        Content content = apiResponse.getContent();
                        if (null != content) {
                            content.forEach((k, v) -> {
                                String $ref = v.getSchema().get$ref();
                                if (StrUtil.isNotBlank($ref)) {
                                    List<Parameter> responseList = getSchemaProperties(schemaMap, $ref, baiDuFy);
                                    endpoint.setResponses(responseList);
                                    endpoint.setResponseSize(responseList.size());
                                }
                            });
                        }
                    }
                }

                // endregion

                endpoints.add(endpoint);
            });
        });
        return endpoints;
    }

    /**
     * 构建分组标签
     *
     * @param tags      待处理分组标签
     * @param endpoints 接口列表
     * @return {@link List }<{@link Resource }> 最终分组
     * @author song_jx
     */
    private List<Resource> buildTags(List<Tag> tags, final List<Endpoint> endpoints) {
        List<Resource> resources = new ArrayList<Resource>(tags.size());
        tags.forEach(tag -> {
            Resource resource = new Resource();
            resource.setName(tag.getName());
            resource.setDescription(tag.getDescription());
            resource.setEndpoints(
                    endpoints.stream()
                            .filter(path -> (null != path.getTags() && path.getTags().contains(tag.getName())))
                            .collect(Collectors.toList())
            );
            resources.add(resource);
        });
        return resources;
    }

    /**
     * 获取操作映射
     *
     * @param pathItem 路径
     * @return {@link Map }<{@link HttpMethod }, {@link Operation }>
     * @author song_jx
     */
    private Map<HttpMethod, Operation> getOperationMap(PathItem pathItem) {
        Map<HttpMethod, Operation> result = new LinkedHashMap<HttpMethod, Operation>();

        Operation get = pathItem.getGet();
        Operation put = pathItem.getPut();
        Operation post = pathItem.getPost();
        Operation delete = pathItem.getDelete();
        Operation head = pathItem.getHead();
        Operation options = pathItem.getOptions();
        Operation patch = pathItem.getPatch();

        if (get != null) {
            result.put(HttpMethod.GET, get);
        }

        if (put != null) {
            result.put(HttpMethod.PUT, put);
        }
        if (post != null) {
            result.put(HttpMethod.POST, post);
        }
        if (delete != null) {
            result.put(HttpMethod.DELETE, delete);
        }
        if (patch != null) {
            result.put(HttpMethod.PATCH, patch);
        }
        if (head != null) {
            result.put(HttpMethod.HEAD, head);
        }
        if (options != null) {
            result.put(HttpMethod.OPTIONS, options);
        }

        return result;
    }

    /**
     * 递归获取模式属性
     *
     * @param schemaMap 模式映射
     * @param dtoPath   dto路径
     * @param baiDuFy   百度翻译
     * @return {@link List }<{@link Parameter }>
     * @author song_jx
     */
    private List<Parameter> getSchemaProperties(Map<String, Schema> schemaMap, String dtoPath, final BaiDuFy baiDuFy) {
        String schemaDTO = StrUtil.subAfter(dtoPath, StrUtil.SLASH, Boolean.TRUE);
        Schema schema = schemaMap.get(schemaDTO);
        Map<String, Schema> properties = schema.getProperties();
        if (StrUtil.startWith(schemaDTO, "Result")) {
            Schema data = properties.get("data");
            if (null != data && null != data.get$ref()) {
                return getSchemaProperties(schemaMap, data.get$ref(), baiDuFy);
            }
        } else if (StrUtil.startWith(schemaDTO, "IPage")) {
            Schema records = properties.get("records");
            if (null != records) {
                Schema<?> items = ((ArraySchema) records).getItems();
                if (null != items && null != items.get$ref()) {
                    return getSchemaProperties(schemaMap, items.get$ref(), baiDuFy);
                }
            }
        }

        List<Parameter> parameterList = new ArrayList<Parameter>(properties.size());

        List<String> requiredList = schema.getRequired();
        properties.forEach((p, s) -> {
            String description = s.getDescription();
            // 翻译字段
            if (null != baiDuFy) {
                if (StrUtil.isBlank(description)) {
                    description = baiDuFy.translate(p);
                }
            }

            Parameter parameter = new Parameter();
            parameter.setName(p);
            parameter.setDescription(description);
            parameter.setIn("body");
            parameter.setType(s.getType());
            parameter.setRequired(CollUtil.contains(requiredList, p));

            parameterList.add(parameter);
        });

        return parameterList;
    }

}
